/*
 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Set;

/**
 * <code>ActionMap</code> provides mappings from
 * <code>Object</code>s
 * (called <em>keys</em> or <em><code>Action</code> names</em>)
 * to <code>Action</code>s.
 * An <code>ActionMap</code> is usually used with an <code>InputMap</code>
 * to locate a particular action
 * when a key is pressed. As with <code>InputMap</code>,
 * an <code>ActionMap</code> can have a parent
 * that is searched for keys not defined in the <code>ActionMap</code>.
 * <p>As with <code>InputMap</code> if you create a cycle, eg:
 * <pre>
 *   ActionMap am = new ActionMap();
 *   ActionMap bm = new ActionMap():
 *   am.setParent(bm);
 *   bm.setParent(am);
 * </pre>
 * some of the methods will cause a StackOverflowError to be thrown.
 *
 * @author Scott Violet
 * @see InputMap
 * @since 1.3
 */
@SuppressWarnings("serial")
public class ActionMap implements Serializable {

  /**
   * Handles the mapping between Action name and Action.
   */
  private transient ArrayTable arrayTable;
  /**
   * Parent that handles any bindings we don't contain.
   */
  private ActionMap parent;


  /**
   * Creates an <code>ActionMap</code> with no parent and no mappings.
   */
  public ActionMap() {
  }

  /**
   * Sets this <code>ActionMap</code>'s parent.
   *
   * @param map the <code>ActionMap</code> that is the parent of this one
   */
  public void setParent(ActionMap map) {
    this.parent = map;
  }

  /**
   * Returns this <code>ActionMap</code>'s parent.
   *
   * @return the <code>ActionMap</code> that is the parent of this one, or null if this
   * <code>ActionMap</code> has no parent
   */
  public ActionMap getParent() {
    return parent;
  }

  /**
   * Adds a binding for <code>key</code> to <code>action</code>.
   * If <code>action</code> is null, this removes the current binding
   * for <code>key</code>.
   * <p>In most instances, <code>key</code> will be
   * <code>action.getValue(NAME)</code>.
   */
  public void put(Object key, Action action) {
    if (key == null) {
      return;
    }
    if (action == null) {
      remove(key);
    } else {
      if (arrayTable == null) {
        arrayTable = new ArrayTable();
      }
      arrayTable.put(key, action);
    }
  }

  /**
   * Returns the binding for <code>key</code>, messaging the
   * parent <code>ActionMap</code> if the binding is not locally defined.
   */
  public Action get(Object key) {
    Action value = (arrayTable == null) ? null :
        (Action) arrayTable.get(key);

    if (value == null) {
      ActionMap parent = getParent();

      if (parent != null) {
        return parent.get(key);
      }
    }
    return value;
  }

  /**
   * Removes the binding for <code>key</code> from this <code>ActionMap</code>.
   */
  public void remove(Object key) {
    if (arrayTable != null) {
      arrayTable.remove(key);
    }
  }

  /**
   * Removes all the mappings from this <code>ActionMap</code>.
   */
  public void clear() {
    if (arrayTable != null) {
      arrayTable.clear();
    }
  }

  /**
   * Returns the <code>Action</code> names that are bound in this <code>ActionMap</code>.
   */
  public Object[] keys() {
    if (arrayTable == null) {
      return null;
    }
    return arrayTable.getKeys(null);
  }

  /**
   * Returns the number of bindings in this {@code ActionMap}.
   *
   * @return the number of bindings in this {@code ActionMap}
   */
  public int size() {
    if (arrayTable == null) {
      return 0;
    }
    return arrayTable.size();
  }

  /**
   * Returns an array of the keys defined in this <code>ActionMap</code> and
   * its parent. This method differs from <code>keys()</code> in that
   * this method includes the keys defined in the parent.
   */
  public Object[] allKeys() {
    int count = size();
    ActionMap parent = getParent();

    if (count == 0) {
      if (parent != null) {
        return parent.allKeys();
      }
      return keys();
    }
    if (parent == null) {
      return keys();
    }
    Object[] keys = keys();
    Object[] pKeys = parent.allKeys();

    if (pKeys == null) {
      return keys;
    }
    if (keys == null) {
      // Should only happen if size() != keys.length, which should only
      // happen if mutated from multiple threads (or a bogus subclass).
      return pKeys;
    }

    HashMap<Object, Object> keyMap = new HashMap<Object, Object>();
    int counter;

    for (counter = keys.length - 1; counter >= 0; counter--) {
      keyMap.put(keys[counter], keys[counter]);
    }
    for (counter = pKeys.length - 1; counter >= 0; counter--) {
      keyMap.put(pKeys[counter], pKeys[counter]);
    }
    return keyMap.keySet().toArray();
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();

    ArrayTable.writeArrayTable(s, arrayTable);
  }

  private void readObject(ObjectInputStream s) throws ClassNotFoundException,
      IOException {
    s.defaultReadObject();
    for (int counter = s.readInt() - 1; counter >= 0; counter--) {
      put(s.readObject(), (Action) s.readObject());
    }
  }
}
